import StringIter from "./StringIter";
import {TokenType} from "./TokenType";
import {Token} from "./Token";
import TokenizeError from "../error/TokenizeError";
import {ErrorCode} from "../error/ErrorCode";

export default class Tokenizer {

    private it: StringIter;
    private lastToken: Token;

    constructor(it: StringIter) {
        this.it = it;
    }

    nextToken() {
        let token = this.lexNextToken();
        this.lastToken = token;
        return token;
    }

    lexNextToken() {
        this.skipSpaceCharacters();

        if (this.it.isEOF()) {
            return new Token(TokenType.EOF, "", this.it.currentPos(), this.it.currentPos());
        }

        const peek = this.it.peekChar();
        if (this.lastToken?.value === ">" && peek !== "<") {
            return this.lexString();
        } else if (this.isAlphabetic(peek) || peek == '_') {
            return this.lexIdentOrKeyWord();
        } else {
            return this.lexOperatorOrUnknown();
        }
    }


    private lexIdentOrKeyWord() {
        const start = this.it.currentPos();
        let value = "";

        do {
            value += this.it.nextChar();
        } while (this.isLetterOrDigit(this.it.peekChar()) || this.it.peekChar() === '_');

        let tokenType: TokenType;
        switch (value) {
            case "list":
                tokenType = TokenType.List;
                break;
            default:
                tokenType = TokenType.Ident;
        }
        return new Token(tokenType, value, start, this.it.currentPos());
    }

    private lexString() {
        const start = this.it.currentPos();
        let value = "";
        do {
            value += this.it.nextChar();
        } while (this.it.peekChar() !== "<");

        return new Token(TokenType.String, value, start, this.it.currentPos());

    }

    private lexOperatorOrUnknown(): Token {
        const start = this.it.currentPos();
        const v = this.it.nextChar();
        switch (v) {
            case "-":
                return new Token(TokenType.Nege, '-', start, this.it.currentPos());
            case "/":
                return new Token(TokenType.Div, '/', start, this.it.currentPos());
            case "<":
                if (this.it.peekChar() === "/") {
                    this.it.nextChar();
                    return new Token(TokenType.EndTag, '</', start, this.it.currentPos());
                }
                return new Token(TokenType.Lt, '<', start, this.it.currentPos());
            case ">":
                return new Token(TokenType.Gt, '>', start, this.it.currentPos());
            default:
                throw new TokenizeError(ErrorCode.InvalidIdentifier, this.it.previousPos());
        }
    }


    private skipSpaceCharacters(): void {
        while (!this.it.isEOF() && this.isWhitespace(this.it.peekChar())) {
            this.it.nextChar();
        }
    }

    private isWhitespace(v: string): boolean {
        return v === " " || v === "\n" || v === "\t";
    }

    private isAlphabetic(v: string): boolean {
        return /^[a-z|A-Z]$/.test(v);
    }

    private isLetterOrDigit(v: string) {
        return /^[a-z|A-Z|0-9]$/.test(v);
    }

    private isString(v: string) {
        return /^\w$/.test(v);
    }
}